import sys
import os
import socket
import select
import logging

NEW_TAG = "NEW_TAG"
TERMINAL_CLOSE = "TERMINAL_CLOSE"
SINK_CLOSE = "SINK_CLOSE"
PAYLOAD = "PAYLOAD"

class buffer:
    def __init__(self):
        self.bufferList = []
        self.closed = False
    def handle(self, cont):
        self.bufferList.append(cont)
    def close(self):
        self.closed = True
    def drain(self, handler):
        for cont in self.bufferList:
            handler.handle(cont)
        if self.closed:
            handler.close()
        self.bufferList = []

class terminal:
    def __init__(self, tag, multiplexer):
        self.tag = tag
        self.multiplexer = multiplexer

    def write(self, buffer):
        self.multiplexer.writeln(PAYLOAD)
        self.multiplexer.writeln(self.tag)
        self.multiplexer.writeln(str(len(buffer)))
        self.multiplexer.write(buffer)
        self.multiplexer.flush()

    def close(self):
        self.multiplexer.writeln(TERMINAL_CLOSE)
        self.multiplexer.writeln(self.tag)
        self.multiplexer.flush()

class multiplexer:
    def __init__(self, oStream, iStream):
        self.oStream = oStream
        self.iFd = iStream.fileno()
        self.terminalMap = {}
        self.handlers = {}
        
    def getTerminal(self, tag):
        try:
            return self.terminalMap[tag] 
        except KeyError:
            newTerminal = terminal(tag, self)
            self.terminalMap[tag] = newTerminal
            self.sendNewTag(tag)
            return newTerminal

    def registerHandler(self, tag, handler):
        if self.handlers.has_key(tag):
            if isinstance(self.handlers[tag], buffer):
                self.handlers[tag].drain(handler)
        self.handlers[tag] = handler

    def fileno(self):
        return self.iFd

    def sendNewTag(self, tag):
        self.writeln(NEW_TAG)
        self.writeln(tag)
        self.flush()
            
    def write(self, str):
        logging.debug("<OU "+ str)
        self.oStream.write(str)

    def writeln(self, str):
        logging.debug("<OU "+ str)
        self.oStream.write(str + "\n")

    def flush(self):
        self.oStream.flush()

    def _readline(self):
        result = []
        while True:
            c = os.read(self.iFd, 1)
            if len(c) == 0:
                return ""
            result.append(c)
            if c == "\n":
                return "".join(result)
    
    def readstr(self):
        line = self._readline()
        if line[-1:] == "\n":
            line =  line[:-1]
        logging.debug("<IN "+ line)
        return line

    def close(self):
        for handler in self.handlers.values():
            handler.close()
        self.handlers = {}

    def _full_read(self, fd, length):
        cont = ""
        while len(cont) < length:
            cont += os.read(self.iFd, length-len(cont))
        logging.debug("<IN "+ cont)
        return cont

    def handleIn(self):
        cmd = self.readstr()
        if cmd == "":
            sys.exit(0)
        elif cmd == NEW_TAG:
            tag = self.readstr() 
            if not self.handlers.has_key(tag):
                self.handlers[tag] = buffer()
        elif cmd == PAYLOAD:
            tag = self.readstr()
            length = int(self.readstr())
            cont = self._full_read(self.iFd, length)
            self.handlers[tag].handle(cont)
        elif cmd == TERMINAL_CLOSE:
            tag = self.readstr()
            self.handlers[tag].close()
        elif cmd == ""  or cmd == SINK_CLOSE:
            # looks like the connection is closed()
            self.close()
            return False
        else:
            sys.stderr.write("error! '" + cmd + "'\n")
            return False
        return True

    def start(self):
        while self.handle():
            pass
